﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using Niknak.Services.ServiceCore;
using Niknak.Services.ServiceCore.TaskScheduler;
using Niknak.Services.TaskScheduler.Interfaces;

namespace Niknak.Services.TaskScheduler
{
    public class AdvancedTask<ExceptionHandlerType, ScheduledTaskType, AuditLogType, AuditLogEntryType> : AdvancedTaskProcessor<AdvancedTaskConfig<ScheduledTaskType>>, IConfiguredTask<AdvancedTaskConfig<ScheduledTaskType>>, IControlledTask
        where ExceptionHandlerType : IExceptionHandler, new()
        where ScheduledTaskType : Interfaces.DataModel.IScheduledTask, new()
        where AuditLogType : Interfaces.DataModel.IAuditLog<AuditLogEntryType>, new()
        where AuditLogEntryType : Interfaces.DataModel.IAuditLogEntry
    {
        protected static List<Guid> _TasksInProgress = new List<Guid>();
        public static Guid[] TaskIDsInProgress { get { return _TasksInProgress.ToArray(); } }
        protected bool _TerminationRequested = false;

        public ICustomTaskConfig CustomSettings { get { return this.Settings; } }
        public bool IsRunning { get { return base._TaskInProgress; } }

        public static ExceptionHandlerType CustomExceptionHandler
        {
            get { return new ExceptionHandlerType(); }
        }

        private ITaskProcessor<AdvancedTaskConfig<ScheduledTaskType>, AuditLogType, AuditLogEntryType> _ProcessorInstance = null;
        private ITaskProcessor<AdvancedTaskConfig<ScheduledTaskType>, AuditLogType, AuditLogEntryType> ProcessorInstance
        {
            get
            {
                if (_ProcessorInstance == null)
                    _ProcessorInstance = Utils.CreateInstance<ITaskProcessor<AdvancedTaskConfig<ScheduledTaskType>, AuditLogType, AuditLogEntryType>>(this.Settings.CLRType);
                return _ProcessorInstance;
            }
        }

        public AdvancedTask() { }

        public AdvancedTask(ScheduledTaskType fromScheduledTask)
        {
            LoadConfig(new AdvancedTaskConfig<ScheduledTaskType>(fromScheduledTask));
        }

        public AdvancedTask(AdvancedTaskConfig<ScheduledTaskType> fromConfig)
        {
            LoadConfig(fromConfig);
        }

        public void LoadConfig(AdvancedTaskConfig<ScheduledTaskType> config)
        {
            this.Settings = config;
        }

        public override void Initialise()
        {
            _TerminationRequested = false;
            base._TaskProcessor = ProcessIteration;
            base.LogMessageGenerated += new EventHandler<LoggingEventArgs>(AdvancedTask_LogMessageGenerated);
            base.IterationStarting += new EventHandler<IterationEventArgs>(AdvancedTask_IterationStarting);
            base.IterationTerminationRequest += new EventHandler<IterationEventArgs>(AdvancedTask_IterationTerminationRequest);
            base.IterationCompleted += new EventHandler<IterationEventArgs>(AdvancedTask_IterationCompleted);
            base.TaskShuttingDown += new EventHandler(AdvancedTask_TaskShuttingDown);

            //if this raises an exception it gets caught by Engine
            ProcessorInstance.Processor_Load(CustomSettings.StartArguments);

            base.Initialise();
        }

        private void AdvancedTask_LogMessageGenerated(object sender, LoggingEventArgs e)
        {
            System.Diagnostics.EventLogEntryType severity = System.Diagnostics.EventLogEntryType.Information;
            if (e.Severity == InternalEnums.TaskScheduler.LoggingMessageSeverity.Warning)
                severity = System.Diagnostics.EventLogEntryType.Warning;
            else if (e.Severity == InternalEnums.TaskScheduler.LoggingMessageSeverity.Error)
                severity = System.Diagnostics.EventLogEntryType.Error;

            if (this.Settings.EnableLogging)
            {
                CustomExceptionHandler.WriteToEventLog("Log event received from task " + ((AdvancedTaskConfig<ScheduledTaskType>)e.ConfigSettings).Name + ": " + e.Message, severity, Constants.EventLogIds.LOGMESSAGEGENERATED);
                ProcessorInstance.RaiseTaskLogMessageGeneratedEvent(sender, e);
            }
        }

        private void AdvancedTask_TaskShuttingDown(object sender, EventArgs e)
        {
            _TerminationRequested = true;
            if (this.Settings.EnableLogging)
                CustomExceptionHandler.WriteToEventLog("TaskShuttingDown event raised for task " + base.Settings.Name, System.Diagnostics.EventLogEntryType.Information, Constants.EventLogIds.TASKSHUTTINGDOWN);

            ProcessorInstance.RaiseTaskShuttingDownEvent(sender, e);
        }

        private void AdvancedTask_IterationStarting(object sender, IterationEventArgs e)
        {
            //WINTER IS COMING
            if (this.Settings.EnableLogging)
                CustomExceptionHandler.WriteToEventLog("IterationStarting event raised for task " + ((AdvancedTaskConfig<ScheduledTaskType>)e.ConfigSettings).Name, System.Diagnostics.EventLogEntryType.Information, Constants.EventLogIds.ITERATIONSTARTING);

            ProcessorInstance.RaiseTaskIterationStartingEvent(sender, e);
        }

        private void AdvancedTask_IterationCompleted(object sender, IterationEventArgs e)
        {
            _TerminationRequested = false;

            if (this.Settings.EnableLogging)
                CustomExceptionHandler.WriteToEventLog("IterationCompleted event raised for task " + ((AdvancedTaskConfig<ScheduledTaskType>)e.ConfigSettings).Name, System.Diagnostics.EventLogEntryType.Information, Constants.EventLogIds.ITERATIONCOMPLETED);

            if (((AdvancedTaskConfig<ScheduledTaskType>)e.ConfigSettings).EnableCompletedEvent || e.GetOverrideFlag())
                ProcessorInstance.RaiseTaskIterationCompletedEvent(sender, e);
        }

        private void AdvancedTask_IterationTerminationRequest(object sender, IterationEventArgs e)
        {
            //update the flag that our process method is watching
            _TerminationRequested = true;
            if (this.Settings.EnableLogging)
                CustomExceptionHandler.WriteToEventLog("IterationTerminationRequest event raised for task " + ((AdvancedTaskConfig<ScheduledTaskType>)e.ConfigSettings).Name, System.Diagnostics.EventLogEntryType.Information, Constants.EventLogIds.ITERATIONTERMINATIONREQUEST);

            ProcessorInstance.RaiseTaskIterationTerminationRequestEvent(sender, e);
        }

        private void ProcessIteration(object obj)
        {
            if (!(obj is AdvancedTaskConfig<ScheduledTaskType>))
            {
                CustomExceptionHandler.ExceptionLogEntry(new Exception("Settings object passed to ProcessIteration was not of type AdvancedTaskConfig"));
                return;
            }

            AdvancedTaskConfig<ScheduledTaskType> config = (AdvancedTaskConfig<ScheduledTaskType>)obj;

            //if we're running a task that's expected to run long-term (i.e. terminate is enabled) get out early if it's still running
            if (!_ShuttingDown && _TasksInProgress.Contains(config.ScheduledTaskId) && config.TerminateAtEndTime)
                return;

            AuditLogType currentLog = new AuditLogType();
            currentLog.AuditLogId = Guid.NewGuid();
            currentLog.EnableLogging = config.EnableLogging;
            currentLog.ScheduledTaskId = config.ScheduledTaskId;
            currentLog.TaskOutcome = Enums.TaskOutcome.InProgress;
            currentLog.StartDate = DateTime.UtcNow;
            
            if (currentLog.EnableLogging)
                currentLog.SaveAuditLog();

            if (_ShuttingDown || _TasksInProgress.Contains(config.ScheduledTaskId))
            {
                currentLog.AddEntry(Enums.AuditLogEntryType.Warning, "Task iteration not run because the service is shutting down or the task is still in progress");
                currentLog.TaskOutcome = Enums.TaskOutcome.Aborted;
                currentLog.FinishDate = DateTime.UtcNow;

                if (currentLog.EnableLogging)
                    currentLog.SaveAuditLog();
                return;
            }

            _TasksInProgress.Add(config.ScheduledTaskId);
            currentLog.AddEntry(Enums.AuditLogEntryType.Information, config.Name + " started");

            try
            {
                //must not return until complete
                currentLog.TaskOutcome = ProcessorInstance.ProcessTask(config, currentLog, ref _TerminationRequested);
            }
            catch (Exception ex)
            {
                CustomExceptionHandler.ExceptionLogEntry(ex);
                currentLog.AddEntry(Enums.AuditLogEntryType.Error, ex.Message);
                currentLog.TaskOutcome = Enums.TaskOutcome.Aborted;
            }

            currentLog.AddEntry(Enums.AuditLogEntryType.Information, config.Name + " finished");
            currentLog.FinishDate = DateTime.UtcNow;

            if (currentLog.EnableLogging)
                currentLog.SaveAuditLog();

            _TasksInProgress.Remove(config.ScheduledTaskId);
        }

        public new void ResetShuttingdown()
        {
            AdvancedTaskProcessor<AdvancedTaskConfig<ScheduledTaskType>>.ResetShuttingdown();
        }
    }
}